﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using AliyunOpenSearch4Net.Base;
using Newtonsoft.Json;

namespace AliyunOpenSearch4Net
{
    /// <summary>
    /// Aliyun OpenSearch Client
    /// </summary>
    public partial class AliyunClient
    {
        private readonly string _aliyunHost;

        private readonly string _accessKeyID;

        private readonly string _accessKeySecret;

        /// <summary>
        /// Initializes a new instance of the <see cref="AliyunClient"/> class.
        /// </summary>
        /// <param name="accessKeyID">The access key ID.</param>
        /// <param name="accessKeySecret">The access key secret.</param>
        public AliyunClient(string accessKeyID, string accessKeySecret)
            : this("http://opensearch-cn-hangzhou.aliyuncs.com", accessKeyID, accessKeySecret)
        {

        }

        /// <summary>
        /// Initializes a new instance of the <see cref="AliyunClient"/> class.
        /// </summary>
        /// <param name="host">The host.</param>
        /// <param name="accessKeyID">The access key ID.</param>
        /// <param name="accessKeySecret">The access key secret.</param>
        /// <exception cref="System.ArgumentNullException">
        /// host
        /// or
        /// accessKey
        /// or
        /// accessKeySecret
        /// </exception>
        public AliyunClient(string host, string accessKeyID, string accessKeySecret)
        {
            if (string.IsNullOrEmpty(host))
                throw new ArgumentNullException("host");

            if (string.IsNullOrEmpty(accessKeyID))
                throw new ArgumentNullException("accessKeyID");

            if (string.IsNullOrEmpty(accessKeySecret))
                throw new ArgumentNullException("accessKeySecret");

            _aliyunHost = host;
            _accessKeyID = accessKeyID;
            _accessKeySecret = accessKeySecret;
        }

        private void WriteRequestBody(Stream requestStream, string body)
        {
            var formData = Encoding.UTF8.GetBytes(body);
            requestStream.Write(formData, 0, formData.Length);
            requestStream.Flush();
        }

        private WebRequest CreatePreparedWebRequest(RequestBase request, string path, string method, out string body)
        {
            var commonQueryInfo = new CommonQueryInfo(_accessKeyID);
            var queryData = request.MakeQueryData(commonQueryInfo);

            // composite the final query
            var queryString = CompositeQuery(method, queryData);

            var isPost = HttpMethod.POST.Equals(method, StringComparison.OrdinalIgnoreCase);

            var uri = _aliyunHost + path;

            // if queryData.AdditionalItems is not empty, still append query parameters after url,
            // and write queryData.AdditionalItems into request body
            if (!isPost || queryData.AdditionalItems.Count > 0)
                uri += "?" + queryString;

            body = string.Empty;

            if (isPost)
            {
                body = queryString;

                // write queryData.AdditionalItems into request body if it has value
                if (queryData.AdditionalItems.Count > 0)
                {
                    body = queryData.AdditionalItems.ToQueryString();
                }
            }

            var httpRequest = HttpWebRequest.Create(uri);
            httpRequest.Method = method;

            return httpRequest;
        }

        private TResponse DeserializeObject<TResponse>(string json)
        {
            return JsonConvert.DeserializeObject<TResponse>(json, new BooleanConverter());
        }

        
        internal string CompositeQuery(string method, QueryDataSet queryData)
        {
            var commonQuery = queryData.CommonQueryItems.ToQueryString();
            var query = queryData.QueryItems.ToQueryString();
            var finalQuery = commonQuery + "&" + query;

            var signature = string.Empty;

            // the additional data also needs be counted in the signature
            if (queryData.AdditionalItems.Count > 0)
            {
                queryData.QueryItems.AddRange(queryData.AdditionalItems);
                query = queryData.QueryItems.ToQueryString();
                signature = ComputeSignature(method, commonQuery + "&" + query);
            }
            else
            {
                signature = ComputeSignature(method, finalQuery);
            }

            return finalQuery + "&Signature=" + signature.CanonicalizedString();
        }

        internal string ComputeSignature(string method, string finalQuery)
        {
            var stringToSign = method + "&%" + ((int)'/').ToString("X") + "&"
                + finalQuery.CanonicalizedString();

            var key = _accessKeySecret + "&";

            var encoding = Encoding.UTF8;

            var hash = new HMACSHA1(encoding.GetBytes(key));
            return Convert.ToBase64String(hash.ComputeHash(encoding.GetBytes(stringToSign)));
        } 
    }
}
